// STL
#include <fstream>  // std::ifstream
#include <iostream> // std::cerr, ::endl
#include <string>   // std::string
#include <tuple>    // std::tuple<>, std::make_tuple()

// Boost
#include <boost/program_options.hpp>   // boost::program_options

// stats++
#include "statsxx/postprocess/ROC.hpp" // ROC_CostMatrix


//
// DESC: Read the parameters file.
//
std::tuple<
           std::string,         // ROC_file
           // =====
           bool,                // create
           // -----
           std::string,         // X_expected_file
           std::string,         // X_predicted_file
           // -----
           bool,                // thresh_avg
           int,                 // nthresh
           int,                 // ta_nROC
           // =====
           bool,                // fit
           // -----
           std::string,         // model
           // -----
           int,                 // fit_nROC
           // -----
           int,                 // model_npts
           // =====
           bool,                // cutoff
           // -----
           std::string,         // cutoff_method
           // -----
           ROC_CostMatrix,      // cost_matrix
           // =====
           std::string          // prefix
           > read_param_file(
                             const std::string param_filename
                             )
{
    namespace po = boost::program_options;

    //=========================================================

    // ----- PARAMETERS -----
    std::string         ROC_file;           // (always) required
    // =====
    bool                create              = false;
    // -----
    std::string         X_expected_file;    // required for creation
    std::string         X_predicted_file;   // required for creation
    // -----
    bool                thresh_avg          = false;
    int                 nthresh;            // required for threshold averaging
    int                 ta_nROC;            // required for threshold averaging
    // =====
    bool                fit                 = false;
    // -----
    std::string         model;              // required for ROC curve estimation (fitting)
    // -----
    int                 fit_nROC;           // required for ROC curve fitting
    // -----
    int                 model_npts          = 100;
    // =====
    bool                cutoff              = false;
    // -----
    std::string         cutoff_method;
    // -----
    ROC_CostMatrix      cost_matrix;
    // =====
    std::string         prefix;

    //=========================================================

    try
    {
        //=========================================================
        // SET THE OPTIONS
        //=========================================================

        po::options_description desc("options");

        desc.add_options()
        ("ROC_file",                  po::value<std::string>(&ROC_file)->required(),                     "ROC filename (input or output)")
        // =====
        ("create.X_expected_file",    po::value<std::string>(&X_expected_file),                          "[create] (expected) examples file")
        ("create.X_predicted_file",   po::value<std::string>(&X_predicted_file),                         "[create] (predicted) examples file")
        // -----
        ("create.thresh_avg",         po::value<bool>(&thresh_avg),                                      "[create] threshold averaging")
        ("create.nthresh",            po::value<int>(&nthresh),                                          "[create] (threshold averaging) number of thresholds")
        ("create.nROC",               po::value<int>(&ta_nROC),                                          "[create] (threshold averaging) number of ROC curves")
        // =====
        ("fit.model",                 po::value<std::string>(&model),                                    "[fit] estimation (fit) model")
        // -----
        ("fit.nROC",                  po::value<int>(&fit_nROC),                                         "[fit] (fit) number of ROC curves")
        // -----
        ("fit.npts",                  po::value<int>(&model_npts),                                       "[fit] (fit) number of (output) points")
        // =====
        ("cutoff.method",             po::value<std::string>(&cutoff_method),                            "[cutoff] method")
        // =====
        ("cost_matrix.P",             po::value<double>(&cost_matrix.P),                                 "[cost_matrix] P")
        ("cost_matrix.FP",            po::value<double>(&cost_matrix.FP),                                "[cost_matrix] FP")
        ("cost_matrix.TN",            po::value<double>(&cost_matrix.TN),                                "[cost_matrix] TN")
        ("cost_matrix.FN",            po::value<double>(&cost_matrix.FN),                                "[cost_matrix] FN")
        ("cost_matrix.TP",            po::value<double>(&cost_matrix.TP),                                "[cost_matrix] TP")
        // =====
        ("prefix",                    po::value<std::string>(&prefix)->required(),                       "prefix for output")
        ;

        //=========================================================
        // READ THE FILE
        //=========================================================

        std::ifstream ifs(param_filename, std::ios::in);

        po::variables_map vm;
        po::store(po::parse_config_file(ifs , desc), vm);
        po::notify(vm);

        ifs.close();

        //=========================================================
        // INFER CALCULATION TYPE(S)
        //=========================================================

        // TODO: it would be nice if we could determine create / train / etc. by just checking if a block (e.g., "[train]" was specified)

        // ----- CREATE -----
        if(
           vm.count("create.X_expected_file")
           )
        {
            create = true;
        }

        // ----- ESTIMATE (FIT) -----
        if(
           vm.count("fit.model")
           )
        {
            fit = true;
        }

        // ----- CUTOFF -----
        if(
           vm.count("cutoff.method")
           )
        {
            cutoff = true;
        }


        //=========================================================
        // CONSISTENCY / ERROR CHECK
        //=========================================================
        //
        // NOTE: ... Default vectors are also set here.

        if( create && (fit || cutoff) )
        {
            std::cerr << "error in read_param_file(): fit or cutoff specified, *in addition* to create" << std::endl;
            //        return false;
            exit(0);
        }

/*
        // TODO: it would be nice to determine interrelations between parameters, rather than all of the messy if checks below
        // TODO: ... but I am not sure that is possible with Boost program_options

        //---------------------------------------------------------
        // CREATE
        //---------------------------------------------------------
        if(create_nunits.size() != create_units_type.size())
        {
            std::cerr << "error in read_param_file(): create_nunits.size() != create_units_type.size()" << '\n';
            //        return false;
            exit(0);
        }

        if(create)
        {
            if(create_nunits.empty())
            {
                std::cerr << "error in read_param_file(): create specified, but create_nunits.empty()" << '\n';
                //        return false;
                exit(0);
            }
        }

        //---------------------------------------------------------
        // TRAIN
        //---------------------------------------------------------
        if(train)
        {
            if(train_X_file.empty())
            {
                std::cerr << "error in read_param_file(): create specified, but train_X_file.empty()" << '\n';
                //        return false;
                exit(0);
            }
        }

        // ----- LEARNING RATE ADJUSTMENTS -----
        if(train_lr_dot.size() != train_lr_adj.size())
        {
            std::cerr << "error in read_param_file(): train_lr_dot.size() != train_lr_adj.size()" << '\n';
            //        return false;
            exit(0);
        }

        if(train_lr_dot.empty())
        {
            train_lr_dot = std::vector<double>(2);
            train_lr_dot[0] = 0.5;
            train_lr_dot[1] = 0.3;
            train_lr_adj = std::vector<double>(2);
            train_lr_adj[0] = 1.05;
            train_lr_adj[1] = 1.01;
        }

        // ----- MOMENTUM ADJUSTMENTS -----
        if(train_p_dot.size() != train_p_adj.size())
        {
            std::cerr << "error in read_param_file(): train_p_dot.size() != train_p_adj.size()" << '\n';
            //        return false;
            exit(0);
        }

        if(train_p_dot.empty())
        {
            train_p_dot = std::vector<double>(1);
            train_p_dot[0] = 0.3;
            train_p_adj = std::vector<double>(1);
            train_p_adj[0] = 1.5;
        }

        //---------------------------------------------------------
        // TEST
        //---------------------------------------------------------
        if(test)
        {
            if(test_X_file.empty())
            {
                std::cerr << "error in read_param_file(): create specified, but test.X_file.empty()" << '\n';
                //        return false;
                exit(0);
            }

            if(test_H_file.empty())
            {
                std::cerr << "error in read_param_file(): create specified, but test.H_file.empty()" << '\n';
                //        return false;
                exit(0);
            }
        }
*/
    }
    catch(std::exception& e)
    {
        std::cerr << "error in read_param_file_0(): " << e.what() << '\n';
//        return false;
        exit(0);
    }
    catch(...)
    {
        std::cerr << "error in read_param_file_0(): unknown" << '\n';
//        return false;
        exit(0);
    }

    //=========================================================
    // FINALIZE AND RETURN
    //=========================================================

    return std::make_tuple(
                           ROC_file,
                           // =====
                           create,
                           // -----
                           X_expected_file,
                           X_predicted_file,
                           // -----
                           thresh_avg,
                           nthresh,
                           ta_nROC,
                           // =====
                           fit,
                           // -----
                           model,
                           // -----
                           fit_nROC,
                           // -----
                           model_npts,
                           // =====
                           cutoff,
                           // -----
                           cutoff_method,
                           // -----
                           cost_matrix,
                           // =====
                           prefix
                           );
}
